package com.daffodil.storage.client;

import java.io.InputStream;
import java.util.List;
import java.util.Objects;

import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import com.daffodil.storage.exception.SeaweedfsException;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * @author yweijian
 * @date 2023年10月27日
 * @version 1.0
 * @description
 * SeaweedFS = &#83;&#101;&#97;&#119;&#101;&#101;&#100;&#70;&#83; exception code = 85500
 */
@Slf4j
public class SeaweedfsClient {

    private String model;

    private String mserver;

    private String endpoint;

    public SeaweedfsClient(String model, String mserver, String endpoint) {
        super();
        this.model = model;
        this.mserver = mserver;
        this.endpoint = endpoint;
    }

    /**
     * 
     * @param folder
     * @param name *.jpg
     * @param limit
     * @return
     * @throws SeaweedfsException
     * 
     *  <p>*        |   any file name</p>
     *  <p>*.jpg    |   abc.jpg</p>
     *  <p>a*.jp*g  |   abc.jpg, abc.jpeg</p>
     *  <p>a*.jp?g  |   abc.jpeg</p>
     */
    public DirPath list(String folder, String namePattern, Integer limit) throws SeaweedfsException {
        Objects.requireNonNull(folder, "folder must not be null");
        Objects.requireNonNull(limit, "limit must not be null");

        if(Model.filer.name().equalsIgnoreCase(this.model)) {
            String url = this.endpoint + StrUtil.SLASH + folder + "?limit=" + limit;
            if(ObjectUtil.isNotEmpty(namePattern)) {
                url += "&namePattern=" + namePattern;
            }
            HttpResponse response = HttpRequest.get(url).header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).timeout(30000).execute();
            if(log.isDebugEnabled()) {
                log.debug(response.headers().toString());
                log.debug(response.body());
            }
            if(response.isOk()) {
                JSONConfig config = new JSONConfig();
                config.setIgnoreCase(true);
                DirPath dirPath = JSONUtil.toBean(response.body(), config, DirPath.class);
                return dirPath;
            }
            throw new SeaweedfsException(85501, "SeaweedFS list storage object error: {}", response.body());
        }
        throw new SeaweedfsException(85502, "SeaweedFS list storage object is not supported");
    }

    /**
     * -获取上传文件编号
     * @return
     */
    public DirAssign dirAssign() throws SeaweedfsException {
        HttpResponse response = HttpRequest.get(this.mserver + "/dir/assign").timeout(30000).execute();
        if(log.isDebugEnabled()) {
            log.debug(response.headers().toString());
            log.debug(response.body());
        }
        if(response.isOk()) {
            return JSONUtil.toBean(response.body(), DirAssign.class);
        }
        throw new SeaweedfsException(85503, "SeaweedFS assign fid error: {}", response.body());
    }

    /**
     * -查看存储卷信息
     * @param volumeId
     * @return
     */
    public DirLookup lookup(String volumeId) throws SeaweedfsException {
        Objects.requireNonNull(volumeId, "volumeId must not be null");
        HttpResponse response = HttpRequest.get(this.mserver + "/dir/lookup?volumeId=" + volumeId).timeout(30000).execute();
        if(log.isDebugEnabled()) {
            log.debug(response.headers().toString());
            log.debug(response.body());
        }
        if(response.isOk()) {
            return JSONUtil.toBean(response.body(), DirLookup.class);
        }
        throw new SeaweedfsException(85504, "SeaweedFS lookup info error: {}", response.body());
    }

    /**
     * -上传文件
     * @param inputStream
     * @param key
     * @param fileName
     * @return
     * @throws SeaweedfsException
     */
    public DirResult upload(InputStream inputStream, String key, String fileName) throws SeaweedfsException {
        Objects.requireNonNull(key, "key must not be null");
        Objects.requireNonNull(fileName, "fileName must not be null");

        byte[] bytes = null;
        try {
            bytes = IoUtil.readBytes(inputStream);
        }catch (IORuntimeException e) {
            throw new SeaweedfsException(85505, e.getMessage());
        }

        if(Model.volume.name().equalsIgnoreCase(this.model)) {
            return this.upload1(key, bytes, fileName);
        }
        if(Model.filer.name().equalsIgnoreCase(this.model)) {
            return this.upload2(key, bytes, fileName);
        }

        throw new SeaweedfsException(85506, "SeaweedFS upload storage object error");
    }

    /**
     * volume服务模式
     * @param key
     * @param bytes
     * @param fileName
     * @return
     */
    private DirResult upload1(String key, byte[] bytes, String fileName) {
        DirAssign assign = this.dirAssign();
        HttpResponse response = HttpRequest.post(this.endpoint + StrUtil.SLASH + assign.getFid()).timeout(30000).form("file", bytes, fileName).execute();
        if(log.isDebugEnabled()) {
            log.debug(response.headers().toString());
            log.debug(response.body());
        }
        if(response.isOk()) {
            DirResult dirResult = JSONUtil.toBean(response.body(), DirResult.class);
            if(null != dirResult) {
                dirResult.setFid(assign.getFid());
            }
            return dirResult;
        }
        throw new SeaweedfsException(85506, "SeaweedFS upload storage object error: {}", response.body());
    }

    /**
     * filer服务模式
     * @param key
     * @param bytes
     * @param fileName
     * @return
     */
    private DirResult upload2(String key, byte[] bytes, String fileName) {
        HttpResponse response = HttpRequest.post(this.endpoint + StrUtil.SLASH + key).timeout(30000).form("file", bytes, fileName).execute();
        if(log.isDebugEnabled()) {
            log.debug(response.headers().toString());
            log.debug(response.body());
        }
        if(response.isOk()) {
            DirResult dirResult = JSONUtil.toBean(response.body(), DirResult.class);
            if(null != dirResult) {
                dirResult.setFid(key);
            }
            return dirResult;
        }
        throw new SeaweedfsException(85506, "SeaweedFS upload storage object error: {}", response.body());
    }

    /**
     * -删除文件
     * @param key
     */
    public Boolean delete(String key) throws SeaweedfsException {
        Objects.requireNonNull(key, "key must not be null");
        HttpResponse response = HttpRequest.delete(this.endpoint + StrUtil.SLASH + key).timeout(30000).execute();
        if(log.isDebugEnabled()) {
            log.debug(response.headers().toString());
            log.debug(response.body());
        }
        if(response.isOk()) {
            return true;
        }
        throw new SeaweedfsException(85507, "SeaweedFS delete storage object error: {}", response.body());
    }

    /**
     * -获取存储对象
     * @param key
     * @return
     */
    public DirFile get(String key) throws SeaweedfsException {
        Objects.requireNonNull(key, "key must not be null");
        HttpResponse response = HttpRequest.get(this.endpoint + StrUtil.SLASH + key).timeout(30000).execute();
        if(log.isDebugEnabled()) {
            log.debug(response.headers().toString());
            log.debug(response.body());
        }
        if(response.isOk()) {
            ContentDisposition contentDisposition = ContentDisposition.parse(response.header(HttpHeaders.CONTENT_DISPOSITION));
            DirFile dirFile = new DirFile();
            dirFile.setETag(response.header("Etag"));
            dirFile.setFilename(contentDisposition.getFilename());
            dirFile.setMineType(response.header("Content-Type"));
            dirFile.setSize(Long.parseLong(response.header("Content-Length")));
            dirFile.setInputStream(response.bodyStream());
            return dirFile;
        }
        throw new SeaweedfsException(85508, "SeaweedFS get storage object error: {}", response.body());
    }

    public enum Model {
        volume,filer;
    }

    public class DirAssign {

        private String fid;

        private String url;

        private String publicUrl;

        private Integer count;

        public String getFid() {
            return fid;
        }

        public String getUrl() {
            return url;
        }

        public String getPublicUrl() {
            return publicUrl;
        }

        public Integer getCount() {
            return count;
        }

        public void setFid(String fid) {
            this.fid = fid;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public void setPublicUrl(String publicUrl) {
            this.publicUrl = publicUrl;
        }

        public void setCount(Integer count) {
            this.count = count;
        }
    }

    public class DirResult {

        private String fid;

        private String name;

        private Long size;

        private String eTag;

        public String getFid() {
            return fid;
        }

        public String getName() {
            return name;
        }

        public Long getSize() {
            return size;
        }

        public String getETag() {
            return eTag;
        }

        public void setFid(String fid) {
            this.fid = fid;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void setSize(Long size) {
            this.size = size;
        }

        public void setETag(String eTag) {
            this.eTag = eTag;
        }
    }

    public class DirLookup {

        private String volumeOrFileId;

        private List<DirLocation> locations;

        public String getVolumeOrFileId() {
            return volumeOrFileId;
        }

        public List<DirLocation> getLocations() {
            return locations;
        }

        public void setVolumeOrFileId(String volumeOrFileId) {
            this.volumeOrFileId = volumeOrFileId;
        }

        public void setLocations(List<DirLocation> locations) {
            this.locations = locations;
        }

    }

    public class DirLocation {

        private String url;

        private String publicUrl;

        private String dataCenter;

        public String getUrl() {
            return url;
        }

        public String getPublicUrl() {
            return publicUrl;
        }

        public String getDataCenter() {
            return dataCenter;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public void setPublicUrl(String publicUrl) {
            this.publicUrl = publicUrl;
        }

        public void setDataCenter(String dataCenter) {
            this.dataCenter = dataCenter;
        }
    }

    public class DirFile {

        private String filename;

        private Long size;

        private String eTag;

        private String mineType;

        private InputStream inputStream;

        public String getFilename() {
            return filename;
        }

        public Long getSize() {
            return size;
        }

        public String getETag() {
            return eTag;
        }

        public String getMineType() {
            return mineType;
        }

        public InputStream getInputStream() {
            return inputStream;
        }

        public void setFilename(String filename) {
            this.filename = filename;
        }

        public void setSize(Long size) {
            this.size = size;
        }

        public void setETag(String eTag) {
            this.eTag = eTag;
        }

        public void setMineType(String mineType) {
            this.mineType = mineType;
        }

        public void setInputStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }
    }

    public class DirPath {

        private String path;

        private Integer limit;

        private List<DirEntrie> entries;

        public String getPath() {
            return path;
        }

        public Integer getLimit() {
            return limit;
        }

        public List<DirEntrie> getEntries() {
            return entries;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public void setLimit(Integer limit) {
            this.limit = limit;
        }

        public void setEntries(List<DirEntrie> entries) {
            this.entries = entries;
        }

    }

    public class DirEntrie {

        private String fullPath;

        private String mime;

        private Long fileSize;

        private List<DirChunk> chunks;

        public String getFullPath() {
            return fullPath;
        }

        public String getMime() {
            return mime;
        }

        public Long getFileSize() {
            return fileSize;
        }

        public List<DirChunk> getChunks() {
            return chunks;
        }

        public void setFullPath(String fullPath) {
            this.fullPath = fullPath;
        }

        public void setMime(String mime) {
            this.mime = mime;
        }

        public void setFileSize(Long fileSize) {
            this.fileSize = fileSize;
        }

        public void setChunks(List<DirChunk> chunks) {
            this.chunks = chunks;
        }
    }

    public class DirChunk {

        private String file_id;

        private Long size;

        private Long modified_ts_ns;

        private String e_tag;

        public String getFile_id() {
            return file_id;
        }

        public Long getSize() {
            return size;
        }

        public Long getModified_ts_ns() {
            return modified_ts_ns;
        }

        public String getE_tag() {
            return e_tag;
        }

        public void setFile_id(String file_id) {
            this.file_id = file_id;
        }

        public void setSize(Long size) {
            this.size = size;
        }

        public void setModified_ts_ns(Long modified_ts_ns) {
            this.modified_ts_ns = modified_ts_ns;
        }

        public void setE_tag(String e_tag) {
            this.e_tag = e_tag;
        }
    }
}
